#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
__all__ = []

from .info import (ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE,
                   TEN, JACK, QUEEN, KING)

MAX_CARDS = 999999


def MoveToTop(cards, func=None, ncards=MAX_CARDS):
    """
     MoveToTop(cards:seq[, func:func, ncards:int=MAX_CARDS])
      -> seq

    Place cards on the top of the talon (i.e. first cards to be dealt)

    :param sequence cards: card sequence.
    :param function cards: filter function that returns [select, sort_order].
      None will locate a card of each suit.
    :param int ncards: place number of card.
    :return: talon top placement.
    :rtype: sequence
    """
    cards, scards = __shuffleHookMoveSorter(cards, func, ncards)
    return cards + scards


def MoveToBottom(cards, func=None, ncards=MAX_CARDS):
    """
     MoveToBottom(cards:seq[, func:func=None, ncards:int=MAX_CARDS]) -> seq

    Place cards on the bottom of the Talon (i.e. last cards to be dealt)

    :param sequence cards: card sequence.
    :param function cards: filter function that returns [select, sort_order].
      None will locate a card of each suit.
    :param int ncards: place number ofcard.
    :return: talon top placement.
    :rtype: sequence
    """
    cards, scards = __shuffleHookMoveSorter(cards, func, ncards)
    return scards + cards

_SORT = lambda c: (c['rank'], c['deck'], c['suit'], )

move = lambda c, ranks=[], deck=None, suits=[]: (
                               (True if not ranks else c['rank'] in ranks) and
                               (True if deck is None else c['deck'] == deck) and
                               (True if not suits else c['suit'] in suits),
                               None,)

OneAce = lambda c, suits=[]: move(c, ranks=[ACE], deck=0, suits=suits)
AllAce = lambda c, suits=[]: move(c, ranks=[ACE], suits=suits)
OneTwo = lambda c, suits=[]: move(c, ranks=[TWO], deck=0, suits=suits)
AllTwo = lambda c, suits=[]: move(c, ranks=[TWO], suits=suits)
OneThree = lambda c, suits=[]: move(c, ranks=[THREE], deck=0, suits=suits)
AllThree = lambda c, suits=[]: move(c, ranks=[THREE], suits=suits)
OneFour = lambda c, suits=[]: move(c, ranks=[FOUR], deck=0, suits=suits)
AllFour = lambda c, suits=[]: move(c, ranks=[FOUR], suits=suits)
OneFive = lambda c, suits=[]: move(c, ranks=[FIVE], deck=0, suits=suits)
AllSix = lambda c, suits=[]: move(c, ranks=[FIVE], suits=suits)
OneSix = lambda c, suits=[]: move(c, ranks=[SIX], deck=0, suits=suits)
AllSix = lambda c, suits=[]: move(c, ranks=[SIX], suits=suits)
OneSeven = lambda c, suits=[]: move(c, ranks=[SEVEN], deck=0, suits=suits)
AllSeven = lambda c, suits=[]: move(c, ranks=[SEVEN], suits=suits)
OneEight = lambda c, suits=[]: move(c, ranks=[EIGHT], deck=0, suits=suits)
AllEight = lambda c, suits=[]: move(c, ranks=[EIGHT], suits=suits)
OneNine = lambda c, suits=[]: move(c, ranks=[NINE], deck=0, suits=suits)
AllNine = lambda c, suits=[]: move(c, ranks=[NINE], suits=suits)
OneTen = lambda c, suits=[]: move(c, ranks=[TEN], deck=0, suits=suits)
AllTen = lambda c, suits=[]: move(c, ranks=[TEN], suits=suits)
OneJack = lambda c, suits=[]: move(c, ranks=[JACK], deck=0, suits=suits)
AllJack = lambda c, suits=[]: move(c, ranks=[JACK], suits=suits)
OneQueen = lambda c, suits=[]: move(c, ranks=[QUEEN], deck=0, suits=suits)
AllQueen = lambda c, suits=[]: move(c, ranks=[QUEEN], suits=suits)
OneKing = lambda c, suits=[]: move(c, ranks=[KING], deck=0, suits=suits)
AllKing = lambda c, suits=[]: move(c, ranks=[KING], suits=suits)

OneAceKing = lambda c, suits=[]: move(c, ranks=[ACE, KING], deck=0, suits=suits)
OneTwoKing = lambda c, suits=[]: move(c, ranks=[TWO, KING], deck=0, suits=suits)
OneJackQueen = lambda c, suits=[]: move(c, ranks=[JACK, QUEEN], deck=0,
                                        suits=suits)

AllAceTwo = lambda c, suits=[]: move(c, ranks=[ACE, TWO], suits=suits)
AllFiveSix = lambda c, suits=[]: move(c, ranks=[FIVE, SIX], suits=suits)
AllSixSeven = lambda c, suits=[]: move(c, ranks=[SIX, SEVEN], suits=suits)
AllJackQueen = lambda c, suits=[]: move(c, ranks=[JACK, QUEEN], suits=suits)


OneAceHeart = lambda c: OneAce(c, ranks=[ACE], suits=[2])

OneJackQueenDiamonds = lambda c: (OneJackQueen(c) if c['suit'] == 3
                                                  else OneJack(c))
AcesTwos = lambda c: (AllAce(c, suits=[0, 1]) if c['rank'] == ACE
                                              else AllTwo(c, suits=[2, 3]))
AceBlackDiamond = lambda c: (OneAce(c) if c['suit'] == 3
                                       else AllAce(c, suits=[0, 1]))
OneQcsdKh = lambda c: (OneKing(c) if c['suit'] == 2
                                  else OneQueen(c))
OneTwoAllAce = lambda c: (OneTwo(c) if c['rank'] == TWO
                                  else AllAce(c))

SeqSuits = lambda c: ((c['rank'] == ACE and c['suit'] == ACE) or
                     (c['rank'] == TWO and c['suit'] == TWO) or
                     (c['rank'] == THREE and c['suit'] == THREE) or
                     (c['rank'] == FOUR and c['suit'] == FOUR), None)


def AnyOneRank(cards):
    """
    AnyOneRank(cards:seq) -> seq

    Find all card of same rank.

    :param sequence cards: card sequence.
    :return: is card of same rank, sort order
    :rtype: sequence
    """
    return lambda c, rank=cards[0]['rank']: (c['rank'] == rank, None, )


def __shuffleHookMoveSorter(cards, func, ncards):
    """
    _shuffleHookMoveSorter(cards:seq, func:func, ncards:int) -> seq

    Put cards aside which func finds while keeping others in sequence.

    :param sequence cards: card sequence.
    :param function cards: filter function that returns [select, sort_order].
    :param sequence cards: card sequence.
    :return: remaining cards and sorted cards put aside.
    :rtype: sequence

    .. note: cards are reversed so a smaller sort_order will be nearer the
      talon top.
    """
    scards, suits = [], []
    for c in cards[:]:
        if any([(func is not None and func(c)[0]),
                (func is None and c['suit'] not in suits and c['deck'] == 0)]):
            cards.remove(c)
            scards.append(c)
            suits.append(c['suit'])
            if len(scards) >= ncards:
                break
    scards.sort(key=_SORT, reverse=True)
    return cards, scards


def placeBaseCard(cards, nrows, rank=KING):
    """
    placeBaseCard(cards:seq, nrows:int[, rank:int=KING) -> seq

    Place cards as base card for a pile group (i.e. first cards on piles)

    :param sequence cards: card sequence.
    :param function cards: filter function that returns [select, sort_order].
      None will locate a card of each suit.
    :param int ncards: place number ofcard.
    :return: talon top placement.
    :rtype: sequence
    """
    i = 0
    for i in (cards['index'](c) for c in cards if c['rank'] == rank):
        j = i % nrows
        while j < i:
            if cards[j]['rank'] != rank:
                cards[i], cards[j] = cards[j], cards[i]
                break
            j += nrows
    return cards


def two_in(cls, cards, ncards, rank):
    """
    two_in_five(cards:seq, rank:int) -> seq

    Ensure no pile has three rank by moving middle rank to bottom.

    :param sequence cards: card sequence.
    :param int rank: card rank ensure two in five.
    :return: talon rank so no more than 2 in ncards placement.
    :rtype: sequence
    """
    swap = None
    for i in range(0, len(cards), ncards):
        if rank in cards[i:i + ncards]:
            pile = [i for i in range(i, i + ncards) if cards[i]['rank'] == rank]
            if len(pile) >= 2:
                swap = pile[1] if len(pile) == 3 else swap
                break
    if swap is not None:
        i = -2 if cards[-1]['rank'] == rank else -1  # paranoia
        cards[swap], cards[i] = cards[i], cards[swap]
    cards.reverse()
    return cards


def fromTop(cards, ncards, rank):
    """
    fromTop(cards:seq, rank:int) -> seq

    Ensure top ncards has no rank by moving them to bottom of cards.

    :param sequence cards: card sequence.
    :param int rank: card rank ensure two in five.
    :return: talon rank in top 8 on bottom placement.
    :rtype: sequence
    """
    i = 0
    while i < ncards:
        c = cards.pop()
        if c['rank'] == rank:
            cards = [c] + cards
        else:
            i += 1
            cards = cards + [c]
    return cards


def oneEach(cards, ranks, suit=-1, ncards=None):
    ncards = ncards or len(ranks)
    topcards = []
    for card in cards[:]:
        if (card['suit'] == suit or suit == -1) and card['rank'] in ranks:
            topcards.append(card)
            cards.remove(card)
            ranks.remove(card['rank'])
        if len(topcards) == ncards:
            break
    topcards.sort(key=lambda c: c['rank'], reverse=True)
    return cards + topcards